// Octree
#pragma once

#include "Vector3.h"
#include "GlutStuff.h"
#include <vector>

#define MAX_STACK_SIZE	(max_depth+5) * 8
#define FMAGNET_INDUCING_DEPTH 10

typedef struct { 

	int* particle_index;
	int particle_num;

} Particle;


class Octree {
	
public:
	typedef struct _NearFMagnetCallBack {

		Octree* ot;

		Vector3 pos;
		float dist2;
		int particle_num;

		int cnt;

		Vector3 mm;
		
		_NearFMagnetCallBack() { 
			dist2 = 100000.0f;
			particle_num = 0;
			cnt = 0;
		};

		__forceinline int size() { return cnt; };

	} NearFMagnetCallBack;


private:
	friend void AddFMagnetInQuad(Octree *ot);

	enum {
		X_AXIS = 0,
		Y_AXIS = 1,
		Z_AXIS = 2
	};

private:

	// static variable
	static void* particle_system;

	static struct _particle {
		Vector3* pos;
		Vector3* vel;
		Vector3* mm;
	} particle;

	static int max_particle;

	static int max_depth;
	static int num_nodes;
	static int minimum_depth;

	static Octree** stack;

private:

	Particle m_particles;

	bool m_hasChild;
	bool m_hasInducingFMagnet;

	Vector3 m_centerPos;
	
	Vector3 m_startPos;
	Vector3 m_endPos;
	
	Octree* m_parent;			//parent node
	Octree* m_child[8];			//child node
	
	int m_depth;					

	Vector3 mm;

	NearFMagnetCallBack m_nearFmagnet;

public:

	Octree() {
		m_particles.particle_num = 0;
		m_particles.particle_index = NULL;

		m_parent = NULL;

		m_depth = 0;

		m_hasChild = false;
		m_hasInducingFMagnet = false;

	};

	~Octree() {

	}

	inline void SetBoundingBox(Vector3& startPos, Vector3& endPos) {
		m_startPos = startPos;
		m_endPos = endPos;

		m_centerPos = (m_startPos + m_endPos) * 0.5f;

	};

	static void SetParticleArray(Vector3* pos, Vector3* vel, Vector3* mm) {
		particle.pos = pos;
		particle.vel = vel;
		particle.mm = mm;

		stack = new Octree*[MAX_STACK_SIZE];
	};

	static void SetParticleSystem(void* ps) { particle_system = ps; };

	static void DeleteStack() { delete [] stack; };

	void CreateIndexList(int num) {
		m_particles.particle_index = new int[num];
		for (int i=0; i<num; i++) {
			m_particles.particle_index[i] = i;
		}
		m_particles.particle_num = num;
	}

	void DeleteIndexList() {
		if (m_particles.particle_index)
			delete[] m_particles.particle_index;
	}

	void DeleteAll() {
		if (m_hasChild) {
			for (int i=0; i<8; i++) {
				m_child[i]->DeleteAll();
				delete m_child[i];
				m_hasChild = false;
			}
		}
	}

	void SubDivide();

	void UpdateOctrees();

	void IncreaseDepth() {
		m_depth++;
		if (m_parent)
			m_parent->IncreaseDepth();			
	}

	int SortIndex(int* indexlist, int num, int axis);

	__forceinline Vector3 GetPtcPos(int idx) {
		return particle.pos[m_particles.particle_index[idx]];
	}

	__forceinline static int GetMaxDepth() { return max_depth; };

	// find the node that vector located
	Octree* FindNode(Vector3& pos);

	// return if aabb and quad are intersecting
	__forceinline bool isIntersectAABB(Vector3& min_aabb, Vector3& max_aabb);


	bool FindNearestPointWithInducedMagnet(Vector3& pos, float max_dist, NearFMagnetCallBack& out);
	bool FindNearestPointWithInducedMagnet(Vector3& pos, Vector3& min_aabb, Vector3& max_aabb, NearFMagnetCallBack& out);
	void FindNearestPointWithInducedMagnet();

	void FindDepthCallBack(int depth, void(*callback)(Octree*));
	void FindDepthFMagnet(int depth);

	__forceinline NearFMagnetCallBack getNearFMagnet() { return m_nearFmagnet; };

	__forceinline bool hasChildFMagnet(void) {
		//BT_PROFILE("hasChildFMagnet");
		if (m_hasChild) {
			for (int i=0; i<8; i++) {
				if (m_child[i]->HasInducingMagnet())
					return true;
			}
		}
		return false;
	}

	static void SetInducedFMagnet(Octree *ot) {
		while (1) {
			ot->m_hasInducingFMagnet = true;
			if (ot->m_parent && !ot->m_parent->m_hasInducingFMagnet)
				ot = ot->m_parent;
			else
				break;
		}
	}

	__forceinline bool HasInducingMagnet() {
		return m_hasInducingFMagnet;
	}

	__forceinline static Vector3 GetPosition(Octree* ot) {
		return ot->m_centerPos;
	}

	static void DrawPointAtCenter(Octree* ot) {
		if (!ot->m_particles.particle_num) return;
		glColor3f(0.1, 0.8, 0.0);
		glBegin(GL_POINTS);
		glVertex3f(ot->m_centerPos.x(), ot->m_centerPos.y(), ot->m_centerPos.z());
		glEnd();
	}

	void DrawLine();

	Particle getm_particles(){
		return m_particles;
	}
	int getmax_particle(){
		return max_particle;
	}
};


